#import "BWStitchView.h"
#import "BWEvenweave.h"
#import "BWGrid.h"
#import "BWStitch.h"
#import "BWStitchArray.h"
#import "BWStitchSelection.h"
#import "BWCursor.h"
#import "BWJunkyard.h"
#import "BWPathelizer.h"

// !!! nuke when multiple selections are completed
#import "BWBorkStitchDocument.h"

// this may be bogus
#import "BWEvenweave.h"

static NSColor *g_grayWash;

NSString *BWStitchView_NewStitchNotification
    = @"BWStitchView new stitch notification";

NSString *BWStitchView_NewStitchKey
    = @"BWStitchView new stitch key";


NSString *BWStitchView_StitchSelectedNotification
    = @"BWStitchView stitch selected notification";

NSString *BWStitchView_StitchUnselectedNotification
    = @"BWStitchView stitch unselected notification";

NSString *BWStitchView_SelectedStitchKey
    = @"BWStitchView selected stitch";


NSString *BWStitchView_PrintAreaChangeNotification
    = @"BWStitchView Print area change notification";

NSString *BWStitchView_Anchor1SquareKey
    = @"BWStitchView Anchor one";

NSString *BWStitchView_Anchor2SquareKey
    = @"BWStitchView Anchor two";


NSString *BWStitchView_StitchBendNotification
    = @"BWStitchView stitch bend notification";


@implementation BWStitchView

- (NSRect) calculatePrintableRect
{
    NSRect rect1, rect2;

    if (printAreaAnchor1.x != BWGrid_NO_SQUARE
        && printAreaAnchor2.x != BWGrid_NO_SQUARE) {
        rect1 = [grid rectForSquare: printAreaAnchor1
                      part: BWGrid_WholeSquare];
        rect2 = [grid rectForSquare: printAreaAnchor2
                      part: BWGrid_WholeSquare];

    } else {
        // try to guess the printable area
        NSEnumerator *enumerator;

        BWGrid_Square minSquare, maxSquare;
        minSquare.x = minSquare.y = 99999;
        maxSquare.x = maxSquare.y = 0.0;
        
        enumerator = [stitches objectEnumerator];
        BWStitch *stitch;

        BWGrid_Square square;

        while ((stitch = [enumerator nextObject])) {
            [stitch getStartSquare: &square  part: NULL];
            minSquare.x = MIN (minSquare.x, square.x);
            minSquare.y = MIN (minSquare.y, square.y);
            maxSquare.x = MAX (maxSquare.x, square.x);
            maxSquare.y = MAX (maxSquare.y, square.y);

            [stitch getEndSquare: &square  part: NULL];
            minSquare.x = MIN (minSquare.x, square.x);
            minSquare.y = MIN (minSquare.y, square.y);
            maxSquare.x = MAX (maxSquare.x, square.x);
            maxSquare.y = MAX (maxSquare.y, square.y);
        }

        if (trackingStitch) {
            [trackStitch getStartSquare: &square  part: NULL];
            minSquare.x = MIN (minSquare.x, square.x);
            minSquare.y = MIN (minSquare.y, square.y);
            maxSquare.x = MAX (maxSquare.x, square.x);
            maxSquare.y = MAX (maxSquare.y, square.y);

            [trackStitch getEndSquare: &square  part: NULL];
            minSquare.x = MIN (minSquare.x, square.x);
            minSquare.y = MIN (minSquare.y, square.y);
            maxSquare.x = MAX (maxSquare.x, square.x);
            maxSquare.y = MAX (maxSquare.y, square.y);
        }

        if (minSquare.x == 99999) {
            minSquare.x = minSquare.y = 0;
        }

        // expand it a bit to get any labels
        if (minSquare.x > 0) {
            minSquare.x--;
        }
        if (minSquare.y > 0) {
            minSquare.y--;
        }
        maxSquare.x++;
        maxSquare.y++;

        rect1 = [grid rectForSquare: minSquare
                      part: BWGrid_WholeSquare];
        rect2 = [grid rectForSquare: maxSquare
                      part: BWGrid_WholeSquare];
    }
    
    NSRect unionRect;
    unionRect = NSUnionRect (rect1, rect2);
        
    unionRect = NSInsetRect (unionRect, -([grid fabricWidth] / 2.0),
                             -([grid fabricWidth] / 2.0));
    
    return (unionRect);

} // calculatePrintableRect



- (id) initForPrintingWithGrid: (BWGrid *) printGrid
                      stitches: (BWStitchArray *) printStitches
                     labelFont: (NSFont *) printLabelFont
              printAreaAnchor1: (BWGrid_Square) anchor1
                       anchor2: (BWGrid_Square) anchor2
{
    // can't init with frame since we don't have a frame yet
    if (self = [super init]) {
        [self setGrid: printGrid];
        [self setStitches: printStitches];
        [self setLabelFont: printLabelFont];
        
        printAreaAnchor1 = anchor1;
        printAreaAnchor2 = anchor2;

        NSRect bounds;
        bounds = [self calculatePrintableRect];

        // doing both seem necessary.  Not setting the frame size
        // causes a "no pages from the document were selected to be
        // printed" complaint
        [self setFrameSize: bounds.size];
        [self setBounds: bounds];

        isPrinting = YES;
    }

    return (self);
    
} // initForPrinting


- (void) awakeFromNib
{
    NSWindow *parentWindow;
    parentWindow = [self window];
    [parentWindow setAcceptsMouseMovedEvents: YES];

    trackPart = BWGrid_InvalidSquare;

    if (g_grayWash == nil) {
        g_grayWash = [NSColor colorWithCalibratedRed: 0.25
                              green: 0.25
                              blue: 0.25
                              alpha: 0.5];
        [g_grayWash retain];
    }

    displayPrintArea = NO;
    printAreaAnchor1.x = BWGrid_NO_SQUARE;
    printAreaAnchor2.x = BWGrid_NO_SQUARE;

    drawBendControls = YES;
    drawSelectionHighlighting = YES;

} // awakeFromNib


- (void) setDelegate: (id) newDelegate
{
    delegate = newDelegate;
} // setDelegate


- (id) delegate
{
    return (delegate);
} // delegate


- (void) dealloc
{
    [trackStitch release];
    [templateStitch release];
    [selectedStitches release];

    [super dealloc];

} // dealloc


- (void) drawLabels: (BWStitch *) stitch
{
    BWPathelizer_StitchMetrics metrics;
    
    metrics = [BWPathelizer calculateMetricsForStitch: stitch
                            inGrid: grid];
    
    BOOL drawStart, drawEnd;
    drawStart = [stitch startLabelVisibility];
    drawEnd = [stitch endLabelVisibility];

    NSPoint startPoint;
    startPoint = metrics.startCenter;

    float angle;
    angle = metrics.stitchAngle;

    float labelDisplacement;
    labelDisplacement = [stitch startLabelDisplacement];
    
    // 2.0 magic number is to give a litle breathing space between the
    // curve of the stitch and the label

    if ([stitch startLabelInsideStitch] ) {
        startPoint.x += cos (angle) * (labelDisplacement + 2.0);
        startPoint.y += sin (angle) * (labelDisplacement + 2.0);
    } else {
        angle += degreesToRadians ([stitch startLabelDisplacementAngle]);
        startPoint.x -= cos (angle) 
            * (metrics.width * 2.5 + labelDisplacement);
        startPoint.y -= sin (angle) 
            * (metrics.width * 2.5 + labelDisplacement);
    }

    NSPoint endPoint;
    endPoint = metrics.endCenter;

    angle = metrics.stitchAngle;
    labelDisplacement = [stitch endLabelDisplacement];

    if ([stitch endLabelInsideStitch]) {
        endPoint.x -= cos (angle) * (labelDisplacement + 2.0);
        endPoint.y -= sin (angle) * (labelDisplacement + 2.0);
    } else {
        angle += degreesToRadians ([stitch endLabelDisplacementAngle]);
        endPoint.x += cos (angle) 
            * (metrics.width * 2.5 + labelDisplacement);
        endPoint.y += sin (angle) 
            * (metrics.width * 2.5 + labelDisplacement);
    }

    NSString *label;

    // this is outside of the drawStart/drawEnd check so that
    // the automatic label values get incremented appropriately, even if the
    // label is hidden
    label = [stitch customStartLabel];

    labelCounter++;
    if (label == nil) {
        label = [NSString stringWithFormat: @"%d", labelCounter];
    }

    if (drawStart) {

        NSSize size;
        size = [label sizeWithAttributes: textAttributes];

        NSRect rect;
        rect.origin.x = startPoint.x - size.width / 2;
        rect.origin.y = startPoint.y - size.height / 2;
        rect.size = size;

        [label drawAtPoint: rect.origin
               withAttributes: textAttributes];

        if ([stitch displayDebugControlPoints]) {
            [NSBezierPath strokeRect: rect];
        }
    }

    label = [stitch customEndLabel];
    labelCounter++;
    if (label == nil) {
        label = [NSString stringWithFormat: @"%d", labelCounter];
    }

    if (drawEnd) {

        NSSize size;
        size = [label sizeWithAttributes: textAttributes];

        NSRect rect;
        rect.origin.x = endPoint.x - size.width / 2;
        rect.origin.y = endPoint.y - size.height / 2;
        rect.size = size;

        [label drawAtPoint: rect.origin
               withAttributes: textAttributes];

        if ([stitch displayDebugControlPoints]) {
            [NSBezierPath strokeRect: rect];
        }
    }
    
} // drawLabels



- (NSRect) bendControlPointRectForStitch: (BWStitch *) stitch
{
    BWPathelizer_StitchMetrics metrics;
    
    metrics = [BWPathelizer calculateMetricsForStitch: stitch
                            inGrid: grid];
    float spotSize = 8;
    NSRect spotRect;

    spotRect.origin.x = metrics.midpoint.x - spotSize / 2;
    spotRect.origin.y = metrics.midpoint.y - spotSize / 2;
    spotRect.size = NSMakeSize (spotSize, spotSize);

    return (spotRect);

} // bendControlPointRectForStitch


- (void) drawStitchControlPoints: (BWStitch *) stitch
{
    NSRect spotRect;
    spotRect = [self bendControlPointRectForStitch: stitch];

    NSBezierPath *path;
    path = [NSBezierPath 
               bezierPathWithOvalInRect: spotRect];

    [path setLineWidth: 4.0];
    [[NSColor whiteColor] set];
    [path stroke];

    [[NSColor purpleColor] set];
    [path fill];

    [path setLineWidth: 1.0];
    [[NSColor blackColor] set];
    [path stroke];
    
} // drawStitchControlPoints


- (void) drawStitch: (BWStitch *) stitch
         withLabels: (BOOL) withLabels
{
    BOOL makeMeTransparent = NO;

    if ([selectedStitches count] > 0
        && ! [selectedStitches isStitchSelected: stitch]) {
        makeMeTransparent = YES;
    }

    NSBezierPath *path;
    path = [BWPathelizer makePathForStitch: stitch
                         inGrid: grid];
    NSColor *color;

    color = [stitch fillColor];
    if (makeMeTransparent) {
        color = [color colorWithAlphaComponent: 0.8];
    }
    [color set];

    [path fill];

    BWStitch_ArrowKind arrow;
    arrow = [stitch arrow];

    if (arrow != BWStitch_NoArrow) {
        NSBezierPath *arrowPath;
        arrowPath = [BWPathelizer makeArrowPathForStitch: stitch
                                  inGrid: grid];
        color = [stitch arrowColor];
        if (makeMeTransparent) {
            color = [color colorWithAlphaComponent: 0.7];
        }

        [color  set];
        [arrowPath fill];
        [arrowPath stroke];

        NSRect bounds;
        bounds = [arrowPath bounds];
    }

    color = [stitch strokeColor];

    if (makeMeTransparent) {
        color = [color colorWithAlphaComponent: 0.7];
    }

    [color set];

    if (drawSelectionHighlighting 
        && [selectedStitches isStitchSelected: stitch]) {
        [path setLineWidth: 3.0];
    }

    [path setLineJoinStyle: NSBevelLineJoinStyle];
    [path stroke];

    if (withLabels) {
        [self drawLabels: stitch];
    }

} // drawStitch



- (void) drawStitches
{
    labelCounter = 0;

    NSEnumerator *enumerator;

    enumerator = [stitches objectEnumerator];

    [NSBezierPath setDefaultLineCapStyle: NSRoundLineCapStyle];

    BWStitch *stitch;
    while ((stitch = [enumerator nextObject])) {
        [self drawStitch: stitch
              withLabels: YES];
    }

} // drawStitches



- (void) drawBehindStitches
{
    int count;
    count = [stitches count];

    int i;
    for (i = 0; i < count - 1; i++) {
        // make fake stitch from end of this one to the beginning of
        // the next one
        
        BWStitch *first, *second;
        first = [stitches stitchAtIndex: i];
        second = [stitches stitchAtIndex: i + 1];

        BWGrid_Square startSquare, endSquare;
        BWGrid_SquarePart startPart, endPart;

        // start at *end* of the first
        [first getEndSquare: &startSquare
               part: &startPart];

        // and connect to *start* of the second
        [second getStartSquare: &endSquare
                part: &endPart];

        float width;
        width = [first width];

        BWStitch *temp;
        temp = [[BWStitch alloc] init];
        
        [temp setStartSquare: startSquare  part: startPart];
        [temp setEndSquare: endSquare  part: endPart];
        [temp setWidth: width];
        [temp setFillColor: [first fillColor]];

        [self drawStitch: temp
              withLabels: NO];

        [temp release];
    }
    
} // drawBehindStitches



- (void) drawTracking
{
    if (trackPart != BWGrid_InvalidSquare) {
        [g_grayWash set];
        NSRect rect;
        rect = [grid rectForSquare: trackSquare
                     part: trackPart];
        [NSBezierPath fillRect: rect];
    }

} // drawTracking


- (void) drawPrintArea
{
    NSRect rect;
    rect = [self calculatePrintableRect];
    
    NSColor *shadeColor;
    shadeColor = [NSColor colorWithCalibratedRed: 0.3
                          green: 0.1
                          blue: 0.6
                          alpha: 0.2];
    
    [NSGraphicsContext saveGraphicsState]; {
        [shadeColor set];
        [NSBezierPath fillRect: rect];
    } [NSGraphicsContext restoreGraphicsState];

} // drawPrintArea


- (void) drawRect: (NSRect) rect
{
    rect = [self bounds];

    [[grid backgroundColor] set];
    [NSBezierPath fillRect: rect];

    [NSGraphicsContext saveGraphicsState]; {

        NSAffineTransform *transform;

        if (isPrinting) {
            [NSBezierPath clipRect: rect];

            rect.size.width += rect.origin.x;
            rect.size.height += rect.origin.y;
            rect.origin = NSZeroPoint;

        } else {
            transform = [NSAffineTransform transform];
            [transform translateXBy: rect.origin.x + 0.5
                       yBy: rect.origin.y + 0.5];
            [transform concat];
        }

        NSRect contentRect;
        contentRect = rect;
        contentRect.origin = NSZeroPoint;

        [NSBezierPath setDefaultLineWidth: 1.0];
        if (showBehindStitches) {
            [self drawBehindStitches];
        }
        
        [grid drawInRect: rect];

        [NSBezierPath setDefaultLineWidth: 1.0];
        [self drawStitches];

        if ([NSGraphicsContext currentContextDrawingToScreen]) {
            [self drawTracking];
        }

        if (trackingStitch
            && [trackStitch getEndPart] != BWGrid_InvalidSquare) {

            [self drawStitch: trackStitch
                  withLabels: YES];
        }

        if (displayPrintArea) {
            [self drawPrintArea];
        }

    } [NSGraphicsContext restoreGraphicsState];


    // draw bendy control on top of everythng
    if ([selectedStitches count] == 1) {
        BWStitch *selectedStitch = [selectedStitches arbitraryStitch];
        [self drawStitchControlPoints: selectedStitch];
    }

    [[NSColor blackColor] set];
    if (highlightBorder) {
        [NSBezierPath setDefaultLineWidth: 6.0];
    } else {
        [NSBezierPath setDefaultLineWidth: 2.0];
    }

    [NSBezierPath strokeRect: [self bounds]];
    
} // drawRect


- (void) setGrid: (BWGrid *) newGrid
{
    [newGrid retain];
    [grid release];

    grid = newGrid;

} // setGrid


- (BWGrid *) grid
{
    return (grid);
} // grid

- (BOOL) isFlipped
{
    return (YES);
} // isFlipped


- (void) removeObserverForStitches
{
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    NSEnumerator *enumerator = [stitches objectEnumerator];

    BWStitch *stitch;
    while (stitch = [enumerator nextObject]) {
        [center removeObserver: self
                name: nil
                object: stitch];
    }
    
} // removeObserverForStitches


- (void) addObserverForStitches
{
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    NSEnumerator *enumerator = [stitches objectEnumerator];

    BWStitch *stitch;
    while (stitch = [enumerator nextObject]) {
        [center addObserver: self
                selector: @selector(handleStitchChangedNotification:)
                name: BWStitch_StitchChangedNotification
                object: stitch];
    }
    
} // addObserverForStitches


- (void) handleStitchChangedNotification: (NSNotification *) notification
{
    [self setNeedsDisplay: YES];
} // handleStitchChangedNotification


- (void) setStitches: (BWStitchArray *) newStitches
{
    [self removeObserverForStitches];

    [newStitches retain];
    [stitches release];

    [self addObserverForStitches];

    stitches = newStitches;

} // setStitches


- (BWStitch *) hitTestStitches: (NSPoint) point
{
    BWStitch *result = nil;

    NSEnumerator *enumerator;

    enumerator = [stitches objectEnumerator];

    BWStitch *stitch;
    while ((stitch = [enumerator nextObject])) {
        // !!! if there's a ton of stitches, see if we need an autorelease
        // !!! pool here
        NSBezierPath *path;
        path = [BWPathelizer makePathForStitch: stitch
                             inGrid: grid];
        if ([path containsPoint: point]) {
            result = stitch;
        }
    }

    return (result);

} // hitTestStitches


- (void) postStitchSelectedNotification: (BWStitch *) stitch
{
    NSNotificationCenter *center;
    center = [NSNotificationCenter defaultCenter];

    NSDictionary *userInfo;
    userInfo = [NSDictionary 
                   dictionaryWithObject: 
                       (stitch == nil) 
                   ? (id)[NSNull null] : stitch
                   forKey: BWStitchView_SelectedStitchKey];
    
    [center postNotificationName: BWStitchView_StitchSelectedNotification
            object: self
            userInfo: userInfo];
    
} // postStitchSelectedNotification


- (void) postStitchUnselectedNotification: (BWStitch *) stitch
{
    NSNotificationCenter *center;
    center = [NSNotificationCenter defaultCenter];

    NSDictionary *userInfo;
    userInfo = [NSDictionary 
                   dictionaryWithObject: stitch
                   forKey: BWStitchView_SelectedStitchKey];
    
    [center postNotificationName: BWStitchView_StitchUnselectedNotification
            object: self
            userInfo: userInfo];
    
} // postStitchUnselectedNotification


- (void) runEventTrackingRunLoop
{
    [[NSRunLoop currentRunLoop] limitDateForMode: NSEventTrackingRunLoopMode];
    NSEventType eventType = NSLeftMouseDown;
    
    unsigned int eventMask = NSLeftMouseDownMask | NSLeftMouseUpMask
        | NSLeftMouseDraggedMask | NSMouseMovedMask;
    
    while (eventType != NSLeftMouseUp) {
        NSEvent *event;
        event = [NSApp nextEventMatchingMask: eventMask
                       untilDate: [NSDate distantFuture]
                       inMode: NSEventTrackingRunLoopMode
                       dequeue: YES];
        
        eventType = [event type];
        
        if (eventType == NSLeftMouseDragged) {
            [self mouseDragged: event];
        }
    }
    
} // runEventTrackingRunLoop



- (void) mouseDown: (NSEvent *) event
{
    NSPoint mouse;
    mouse = [self convertPoint: [event locationInWindow]
                  fromView: nil];

    if (trackMode == BWStitchView_Selection) {
        
        BWStitch *selectedStitch;
        selectedStitch = [selectedStitches arbitraryStitch];

        // first see if it's in the tracking point
        if ([selectedStitches count] == 1 && selectedStitch != nil) {
            // first to see if it's a hit in the little drag dot
            NSRect bendControlRect;
            bendControlRect = [self bendControlPointRectForStitch: 
                                        selectedStitch];

            if (NSPointInRect(mouse, bendControlRect)) {
                originalBendFactor = [selectedStitch bendFactor];
                originalBendDisplacement = [selectedStitch bendDisplacement];
                bendTrackStart = mouse;
                trackMode = BWStitchView_BendyControlPoint;
                [self runEventTrackingRunLoop];
                trackMode = BWStitchView_Selection;
                goto done;
            }
        }
        BOOL shiftDown = ([event modifierFlags] & NSShiftKeyMask) ? YES : NO;

        // if no shift key down, unselect everything first
        if (!shiftDown) {
            [selectedStitches unselectAll];
        }

        selectedStitch = [self hitTestStitches: mouse];

        if (shiftDown && [selectedStitches isStitchSelected: selectedStitch]) {
            [self postStitchUnselectedNotification: selectedStitch];
            selectedStitch = nil; // so we don't fall into the next case
            trackMode = BWStitchView_None_ThenSelection;
        } else {
            [self postStitchSelectedNotification: selectedStitch];
            if (shiftDown) {
                trackMode = BWStitchView_None_ThenSelection;
            }
        }

        [self setNeedsDisplay: YES];

        // and don't resize if the shift key is down
        if (!shiftDown && selectedStitch != nil) {

            // now figure out the side
            BWPathelizer_StitchMetrics metrics;
            
            metrics = [BWPathelizer calculateMetricsForStitch: selectedStitch
                                    inGrid: grid];
            
            // look around the end of the stitch
            float radius;
            radius = metrics.width;

            // make it easier to grab on to if the
            // width is skinny
            if (radius < 20.0) {
                radius = 20.0;
            }

            // !!! there's a pathological case - very short stitch which is 
            // !!! very wide
            
            trackSide = trackNone;
            
            // look at start
            if (pointNearPoint (mouse, metrics.startCenter, radius)) {
                trackSide = trackStart;
            } else if (pointNearPoint (mouse, metrics.endCenter, radius)) {
                trackSide = trackEnd;
            } else if ([BWPathelizer metricsAreDegenerate: metrics]) {
                // otherwise there's not any place to grab ahold of
                trackSide = trackStart;
            }

            [self runEventTrackingRunLoop];
        }

        goto done;
    }

    if (trackMode == BWStitchView_Drawing) {
        // possible drawing optimzation - take a snapshot of the playfied
        // as an image, and then draw that
        
        // don't show the tracking part
        trackPart = BWGrid_InvalidSquare;
        
        BWGrid_Square newSquare;
        BWGrid_SquarePart newPart;
        
        // now figure out where we are
        [grid mapPoint: mouse
              toSquare: &newSquare
              part: &newPart
              forceCenterOnly: trackCenterOnly];
        
        if (newPart != BWGrid_InvalidSquare) {
            
            trackingStitch = YES;
            trackStitch = [templateStitch copy];
            [trackStitch retain];
            
            [trackStitch setStartSquare: newSquare
                         part: newPart];
            
            [trackStitch setEndSquare: newSquare
                         part: newPart];
            
            [self setNeedsDisplay: YES];
        }
        goto done;
    }

    if (trackMode == BWStitchView_PrintArea) {
        BWGrid_Square newSquare;

        [grid mapPoint: mouse
              toSquare: &newSquare
              part: NULL
              forceCenterOnly: NO];

        printAreaAnchor1 = newSquare;
        printAreaAnchor2 = newSquare;
        [self setNeedsDisplay: YES];

        trackingPrintArea = YES;
        goto done;
    }

done:
    return;

} // mouseDown


- (void) postStitchBendChange
{
    NSNotificationCenter *center;
    center = [NSNotificationCenter defaultCenter];

    [center postNotificationName: 
                BWStitchView_StitchBendNotification
            object: self
            userInfo: nil];

} // postStitchBendChange


- (void) mouseDragged: (NSEvent *) event
{
    NSPoint mouse;
    mouse = [self convertPoint: [event locationInWindow]
                  fromView: nil];

    // figure out where we are
    BWGrid_Square newSquare;
    BWGrid_SquarePart newPart;
    
    [grid mapPoint: mouse
          toSquare: &newSquare
          part: &newPart
          forceCenterOnly: trackCenterOnly];

    BWStitch *selectedStitch = nil;

    if ([selectedStitches count] == 1) {
        selectedStitch = [selectedStitches arbitraryStitch];
    }

    if (trackMode == BWStitchView_Selection) {
        if (selectedStitch != nil 
            && trackSide != trackNone 
            && newPart != BWGrid_InvalidSquare) {

            BWGrid_Square startSquare, endSquare;
            BWGrid_SquarePart startPart, endPart;

            if (trackSide == trackStart) {
                startSquare = newSquare;
                startPart = newPart;
                [selectedStitch getEndSquare: &endSquare
                                part: &endPart];
            } else if (trackSide == trackEnd) {
                [selectedStitch getStartSquare: &startSquare
                                part: &startPart];
                endSquare = newSquare;
                endPart = newPart;
            }

            [delegate stitchView: self
                      changedStartSquare: startSquare
                      squarePart: startPart
                      endSquare: endSquare
                      squarePart: endPart
                      forStitch: selectedStitch];

            [self setNeedsDisplay: YES];
        }

        goto done;
    }

    if (trackMode == BWStitchView_Drawing) {
        if (trackingStitch) {
            if (newPart != BWGrid_InvalidSquare) {
                [trackStitch setEndSquare: newSquare
                             part: newPart];
            }
            
            [self setNeedsDisplay: YES];
        }
        goto done;
    }

    if (trackMode == BWStitchView_PrintArea) {
        BWGrid_Square newSquare;

        [grid mapPoint: mouse
              toSquare: &newSquare
              part: NULL
              forceCenterOnly: NO];

        printAreaAnchor2 = newSquare;
        [self setNeedsDisplay: YES];

        goto done;
    }

    if (trackMode == BWStitchView_BendyControlPoint) {
        BWPathelizer_StitchMetrics metrics;
        
        metrics = [BWPathelizer calculateMetricsForStitch: selectedStitch
                                inGrid: grid];

        NSAffineTransform *transform = [NSAffineTransform transform];
        [transform setTransformStruct: metrics.rawTransform];
        [transform invert];

        // transform the points into unrotated space
        NSPoint startPoint;
        startPoint = [transform transformPoint: bendTrackStart];
        NSPoint endPoint;
        endPoint = [transform transformPoint: mouse];
            
        float deltaX, deltaY;
        deltaX = startPoint.x - endPoint.x;
        deltaY = startPoint.y - endPoint.y;

        [delegate stitchView: self
                  changedBendFactor: originalBendFactor - deltaY
                  bendDisplacement: originalBendDisplacement - deltaX
                  forStitch: selectedStitch];

        [self setNeedsDisplay: YES];
        [self postStitchBendChange];

        goto done;
    }

done:
    return;

} // mouseDragged


- (void) mouseUp: (NSEvent *) event
{
    NSNotificationCenter *center;
    center = [NSNotificationCenter defaultCenter];

    if (trackMode == BWStitchView_Drawing) {
        if (trackingStitch) {
            
            // make sure it's not a click-stitch
            BWGrid_Square startSquare, endSquare;
            BWGrid_SquarePart startPart, endPart;
            
            [trackStitch getStartSquare: &startSquare
                         part: &startPart];
            [trackStitch getEndSquare: &endSquare
                         part: &endPart];
            
            if (startSquare.x != endSquare.x
                || startSquare.y != endSquare.y
                || startPart != endPart) {
                
                NSDictionary *userInfo;
                userInfo = [NSDictionary dictionaryWithObject: trackStitch
                                         forKey: BWStitchView_NewStitchKey];
                
                [center postNotificationName: 
                            BWStitchView_NewStitchNotification
                        object: self
                        userInfo: userInfo];
            }
            
            trackingStitch = NO;
            [trackStitch release];
            trackStitch = nil;

            [self setNeedsDisplay: YES];
        }
    }

    if (trackMode == BWStitchView_PrintArea) {
        // post notification
        trackingPrintArea = NO;

        NSValue *value1, *value2;
        value1 = [NSValue valueWithBytes: &printAreaAnchor1
                         objCType: @encode(BWGrid_Square)];

        
        value2 = [NSValue valueWithBytes: &printAreaAnchor2
                         objCType: @encode(BWGrid_Square)];

        NSDictionary *userInfo;
        userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                     value1, BWStitchView_Anchor1SquareKey,
                                 value2, BWStitchView_Anchor2SquareKey,
                                 nil];

        [center postNotificationName:
                    BWStitchView_PrintAreaChangeNotification
                object: self
                userInfo: userInfo];
    }

    if (trackMode == BWStitchView_BendyControlPoint) {
        
        trackMode = BWStitchView_Selection;
    }

    if (trackMode == BWStitchView_None_ThenSelection) {
        trackMode = BWStitchView_Selection;
    }

    return;

} // mouseUp



- (void) mouseMoved: (NSEvent *) event
{
    NSPoint mouse;
    mouse = [self convertPoint: [event locationInWindow]
                  fromView: nil];

    if (trackMode == BWStitchView_Drawing) {
        BWGrid_Square newSquare;
        BWGrid_SquarePart newPart;
        
        // now figure out where we are
        [grid mapPoint: mouse
              toSquare: &newSquare
              part: &newPart
              forceCenterOnly: trackCenterOnly];
        
        if (newPart != trackPart
            || newSquare.x != trackSquare.x
            || newSquare.y != trackSquare.y) {
            trackPart = newPart;
            trackSquare = newSquare;
            [self setNeedsDisplay: YES];
        }
    }

} // mouseMoved


- (void) keyDown: (NSEvent *) event
{
    NSString *chars = [event characters];

    if ([chars length] == 0) {
        return;
    }

    unichar character = [chars characterAtIndex: 0];
    
    if (character == NSDeleteCharacter) {
        if ([delegate respondsToSelector: 
                          @selector(deleteSelectedStitchesFromStitchView:)]) {
            [delegate deleteSelectedStitchesFromStitchView: self];
        }
        goto done;
    }

    if ([delegate respondsToSelector:
                      @selector(stitchView:shiftStitchesHorizontal:vertical:)]) {

        if (character == NSRightArrowFunctionKey) {
            [delegate stitchView: self
                      shiftStitchesHorizontal: 1
                      vertical: 0];
            goto done;
        }
        if (character == NSLeftArrowFunctionKey) {
            [delegate stitchView: self
                      shiftStitchesHorizontal: -1
                      vertical: 0];
            goto done;
        }
        if (character == NSUpArrowFunctionKey) {
            [delegate stitchView: self
                      shiftStitchesHorizontal: 0
                      vertical: -1];
            goto done;
        }
        if (character == NSDownArrowFunctionKey) {
            [delegate stitchView: self
                      shiftStitchesHorizontal: 0
                      vertical: 1];
            goto done;
        }
    }
done:
    return;

} // keyDown


// we need to do this to get mouse-moved events

- (BOOL) acceptsFirstResponder
{
    // for mouse-moved
    return (YES);

} // acceptsFirstResponder


// the view changed such that the cursor rects need to be updated

- (void) resetCursorRects
{
    if (trackMode == BWStitchView_Drawing) {
        [self addCursorRect: [self bounds]
              cursor: [BWCursor crosshairCursor]];
    } else {
        [self addCursorRect: [self bounds]
              cursor: [NSCursor arrowCursor]];
        goto done;
    }

done:
    return;

} // resetCursorRects



// 'trackingStitch' is an instance variable, the BOOL if we're tracking
// !!! this is confusing nomenclature

- (void) setTrackingStitch: (BWStitch *) stitch
{
    [stitch retain];
    [trackStitch release];

    trackStitch = stitch;

} // trackingStitch

- (BWStitch *) trackingStitch
{
    return (trackStitch);

} // trackingStitch


- (void) setTemplateStitch: (BWStitch *) stitch
{
    [stitch retain];
    [templateStitch release];

    templateStitch = stitch;

} // templateStitch


- (BWStitch *) templateStitch
{
    return (templateStitch);

} // templateStitch



- (void) setTrackMode: (BWStitchView_TrackMode) mode
{
    trackMode = mode;

    if (mode == BWStitchView_Drawing) {
        // !!! vector through Document?
        [selectedStitches unselectAll];
    }
    if (mode == BWStitchView_Selection) {
        trackPart = BWGrid_InvalidSquare;
    }
    if (mode == BWStitchView_PrintArea) {
        
    }

    [[self window] invalidateCursorRectsForView: self];
    [self setNeedsDisplay: YES];

} // setTrackMode


- (void) setLabelFont: (NSFont *) font
{
    [textAttributes release];

    textAttributes = [NSDictionary dictionaryWithObject: font
                                   forKey: NSFontAttributeName];

    [textAttributes retain];

} // setLabelFont



- (void) setPrintAreaAnchor1: (BWGrid_Square) anchor1
                 anchor2: (BWGrid_Square) anchor2
{
    printAreaAnchor1 = anchor1;
    printAreaAnchor2 = anchor2;

    [self setNeedsDisplay: YES];

} // setPrintAreaTopLeft


- (void) setShowPrintableArea: (BOOL) onOff
{
    displayPrintArea = onOff;
    [self setNeedsDisplay: YES];

} // setShowPrintableArea


- (BOOL) acceptsFirstMouse: (NSEvent *) theEvent
{
    return (YES);
} // acceptsFirstMouse


// supposedly this is a performace boost
- (BOOL) isOpaque
{
    return (YES);
} // isOpaque


- (void) setShowBehindStitches: (BOOL) onOff
{
    showBehindStitches = onOff;

    [self setNeedsDisplay: YES];
    
} // setShowBehindStitches


- (void) setDrawBendControls: (BOOL) onOff
{
    drawBendControls = onOff;
    [self setNeedsDisplay: YES];

} // setDrawBendControls


- (void) setDrawSelectionHighlighting: (BOOL) onOff
{
    drawSelectionHighlighting = onOff;
    [self setNeedsDisplay: YES];

} // setDrawSelectionHighlighting


- (void) setHighlightBorder: (BOOL) onOff
{
    highlightBorder = onOff;
    [self setNeedsDisplay: YES];

} // setHighlightBorder


- (void) unselectAll
{
    // !!! vector through document?
    [selectedStitches unselectAll];
    [self setNeedsDisplay: YES];

} // unselectAll
  

- (void) setTrackCenterOnly: (BOOL) tco
{
    trackCenterOnly = tco;

} // setTrackCenterOnly

- (void) setSelectedStitchSet: (BWStitchSelection *) ss
{
    [ss retain];
    [selectedStitches release];
    selectedStitches = ss;

} // setSelectedStitchSet

@end // BWStitchView

